﻿using System;
using System.Collections.Generic;
using System.Linq;
using System.Text;
using System.Threading.Tasks;

namespace LightCAD.Core
{
    public class Polygon
    {
        private static int IdIndex { get; set; } = 1;
        public int Id;
        public ListEx<Vector3> Points;
        public Plane Plane;
        public Box2 Box2 = null;
        public Box3 Box3 = null;
        public bool HaveArea = true;
        public static object lockObj = new object();
        private const double BackEps = Math.PI / 180 * 1;
        private Polygon()
        {
            lock (lockObj)
            {
                this.Id = IdIndex++;
            }
        }

        public Polygon(ListEx<Vector3> points) : this()
        {
            if (points != null && points.Count > 0)
                this.Reset(points);
        }
        public Polygon(Vector3 p0, Vector3 p1, Vector3 p2) : this(new ListEx<Vector3>() { p0, p1, p2 })
        {
            
        }

        public void Reset(ListEx<Vector3> points)
        {
            this.Points = points;
            this.Box2 = new Box2();
            this.Box3 = new Box3().SetFromPoints(points);
            Vector3 p0 = this.Points[0];
            Vector3 p1 = this.Points[1];
            Vector3 p2 = this.Points[2];
            this.Plane = new Plane().SetFromCoplanarPoints(p0, p1, p2);
            var n = this.Plane.Normal;
            if (p0.DistanceTo(p1) < 0.001 || 
                p1.DistanceTo(p2) < 0.001 || 
                p2.DistanceTo(p0) < 0.001 ||
                n.X == 0 && 
                n.Y == 0 && 
                n.Z == 0 && 
                this.Plane.Constant == 0)
            {
                this.HaveArea = false;
            }
        }

        public bool IsBack(Vector3 cdir, double backEps = BackEps)
        {
            if (this.Plane == null)
                return false; ;

            var angle = cdir.AngleTo(this.Plane.Normal);
            //判断背面 角度不用太严格，稍微大于90度也算背面
            var isBack = double.IsNaN(angle) || angle <= (MathEx.HalfPI + backEps);
            return isBack;
        }

        public Polygon SplitTriangle(Polygon triPoly, out TriCoverState state)
        {
            var parr = triPoly.Points;
            var ext = new JsObj<object>();
            ext["eps"] = 0.01;
            var polys = PlaneSplitTriangle(this.Plane, parr[0], parr[1], parr[2], ext, 0.2);

            var ext_type = ext["type"].ToString();
            switch (ext_type)
            {
                case "front":
                    state = TriCoverState.Front;
                    break;
                case "split":
                    state = TriCoverState.Splite;
                    break;
                case "coin":
                    state = TriCoverState.Coplanar;
                    break;
                default:
                    state = TriCoverState.Back;
                    break;
            }
            if ((state == TriCoverState.Front || state == TriCoverState.Splite || state == TriCoverState.Coplanar) && polys[0] != null)
            {//存在前面的多边形
                Polygon upPoly = null;
                try
                {
                    upPoly = new Polygon(polys[0]);
                }
                catch (Exception err)
                {
                    console.error(err);
                }
                return upPoly;
            }
            else
                return null;
        }

        /// <summary>
        /// 必须保证平面和三角形有交线
        /// </summary>
        /// <param name="plane"></param>
        /// <param name="a"></param>
        /// <param name="b"></param>
        /// <param name="c"></param>
        /// <returns></returns>
        public ListEx<ListEx<Vector3>> PlaneSplitTriangle(Plane plane, Vector3 a, Vector3 b, Vector3 c, JsObj<object> ext = null, double? eps = null)
        {
            //如果三角片被平面分割，那么分割线就是三角片在平面正面部分的底部交线
            var result = new ListEx<ListEx<Vector3>>();
            result.Push(null, null);
            var front = new ListEx<Vector3>();
            var back = new ListEx<Vector3>();
            var dis_a = plane.DistanceToPoint(a);
            var dis_b = plane.DistanceToPoint(b);
            var dis_c = plane.DistanceToPoint(c);
            if (Math.Abs(dis_a + dis_b + dis_c) < 1)
            {

            }
            var _eps = 0.0001;
            if (eps != null)
                _eps = eps.Value;
            else
                eps = _eps;

            //共面，返回空
            if (Utils.IsEqual(dis_a, 0, _eps) && Utils.IsEqual(dis_b, 0, _eps) && Utils.IsEqual(dis_c, 0, _eps))
            {
                if (ext != null) ext["type"] = "coin";
                front.Push(a, b, c);
                result[0] = front;
                if (ext != null)
                {
                    var lines = new ListEx<Vector3>();
                    lines.Push(a, b);
                    lines.Push(b, c);
                    ext["line"] = lines;
                }
                return result;
            }
            var disArr = new List<double> { dis_a, dis_b, dis_c };
            //在当前多边形前面，全部参与遮盖
            if (Utils.FloatGE(dis_a, 0, _eps) && Utils.FloatGE(dis_b, 0, _eps) && Utils.FloatGE(dis_c, 0, _eps) && disArr.FindAll(d => d > _eps).Count >= 1)
            {
                if (ext != null) ext["type"] = "front";
                front.Push(a, b, c);
                result[0] = front;

                var lines = new ListEx<Vector3>();
                if (Utils.IsEqual(dis_a, 0, eps) && Utils.IsEqual(dis_b, 0, eps))
                    lines.Push(a, b);
                if (Utils.IsEqual(dis_a, 0, eps) && Utils.IsEqual(dis_c, 0, eps))
                    lines.Push(a, c);
                if (Utils.IsEqual(dis_b, 0, eps) && Utils.IsEqual(dis_c, 0, eps))
                    lines.Push(b, c);
                if (lines.Length > 0)
                    ext["line"] = lines;
                return result;
            }
            //在当前多边形后面，没有遮盖
            else if (Utils.FloatLE(dis_a, 0, _eps) && Utils.FloatLE(dis_b, 0, _eps) && Utils.FloatLE(dis_c, 0, _eps) && disArr.FindAll(d => d < -_eps).Count >= 1)
            {
                if (ext != null) ext["type"] = "back";
                back.Push(a, b, c);
                result[1] = back;

                var lines = new ListEx<Vector3>();
                if (Utils.IsEqual(dis_a, 0, eps) && Utils.IsEqual(dis_b, 0, eps))
                    lines.Push(a, b);
                if (Utils.IsEqual(dis_a, 0, eps) && Utils.IsEqual(dis_c, 0, eps))
                    lines.Push(a, c);
                if (Utils.IsEqual(dis_b, 0, eps) && Utils.IsEqual(dis_c, 0, eps))
                    lines.Push(b, c);
                if (lines.Length > 0)
                    ext["line"] = lines;
                return result;
            }

            var ins_ab = plane.IntersectLine(new Line3(a, b), new Vector3());
            var ins_bc = plane.IntersectLine(new Line3(b, c), new Vector3());
            var ins_ca = plane.IntersectLine(new Line3(c, a), new Vector3());
            eps = _eps;
            if (ext != null) ext["type"] = "split";
            ListEx<Vector3> splitLine = null;
            if (ext != null)
            {
                splitLine = new ListEx<Vector3>();
                ext["line"] = splitLine;
            }
            if (Utils.IsEqual(dis_a, 0, eps))
            {

                if (dis_b > 0)
                {
                    front.Push(a, b, ins_bc);
                    back.Push(c, a, ins_bc);
                    splitLine.Push(ins_bc, a);
                }
                else
                {
                    front.Push(c, a, ins_bc);
                    back.Push(a, b, ins_bc);
                    splitLine.Push(a, ins_bc);
                }

            }
            else if (Utils.IsEqual(dis_b, 0, eps))
            {
                if (dis_a > 0)
                {
                    front.Push(a, b, ins_ca);
                    back.Push(b, c, ins_ca);
                    splitLine.Push(ins_ca, b);
                }
                else
                {
                    front.Push(b, c, ins_ca);
                    back.Push(a, b, ins_ca);
                    splitLine.Push(b, ins_ca);
                }
            }
            else if (Utils.IsEqual(dis_c, 0, eps))
            {
                if (dis_a > 0)
                {
                    front.Push(c, a, ins_ab);
                    back.Push(b, c, ins_ab);
                    splitLine.Push(ins_ab, c);
                }
                else
                {
                    front.Push(b, c, ins_ab);
                    back.Push(c, a, ins_ab);
                    splitLine.Push(c, ins_ab);
                }
            }
            else if (dis_a > 0 && dis_b > 0)
            {
                front.Push(a, b, ins_bc, ins_ca);
                back.Push(c, ins_ca, ins_bc);
                splitLine.Push(ins_bc, ins_ca);
            }
            else if (dis_a < 0 && dis_b < 0)
            {
                front.Push(c, ins_ca, ins_bc);
                back.Push(a, b, ins_bc, ins_ca);
                splitLine.Push(ins_ca, ins_bc);
            }
            else if (dis_b > 0 && dis_c > 0)
            {
                front.Push(b, c, ins_ca, ins_ab);
                back.Push(a, ins_ab, ins_ca);
                splitLine.Push(ins_ca, ins_ab);
            }
            else if (dis_b < 0 && dis_c < 0)
            {
                front.Push(a, ins_ab, ins_ca);
                back.Push(b, c, ins_ca, ins_ab);
                splitLine.Push(ins_ab, ins_ca);
            }
            else if (dis_c > 0 && dis_a > 0)
            {
                front.Push(c, a, ins_ab, ins_bc);
                back.Push(b, ins_bc, ins_ab);
                splitLine.Push(ins_ab, ins_bc);
            }
            else if (dis_c < 0 && dis_a < 0)
            {
                front.Push(b, ins_bc, ins_ab);
                back.Push(c, a, ins_ab, ins_bc);
                splitLine.Push(ins_bc, ins_ab);
            }
            if (front.Length == 0) front = null;
            if (back.Length == 0) back = null;
            result[0] = front;
            result[1] = back;
            return result;

        }

        public List<Vector2> ToPoint2d()
        {
            return this.Points.Select(p => p.ToVector2()).ToList();
        }
    }
}
